package com.nova.bluetooth.webIat;

import android.os.Environment;

import androidx.annotation.NonNull;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.nova.bluetooth.helpUtils.LogUtil;

import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;

/**
 * 语音听写流式 WebAPI 接口调用示例 接口文档（必看）：https://doc.xfyun.cn/rest_api/语音听写（流式版）.html
 * webapi 听写服务参考帖子（必看）：http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=38947&extra=
 * 语音听写流式WebAPI 服务，热词使用方式：登陆开放平台https://www.xfyun.cn/后，找到控制台--我的应用---语音听写---个性化热词，上传热词
 * 注意：热词只能在识别的时候会增加热词的识别权重，需要注意的是增加相应词条的识别率，但并不是绝对的，具体效果以您测试为准。
 * 错误码链接：https://www.xfyun.cn/document/error-code （code返回错误码时必看）
 * 语音听写流式WebAPI 服务，方言或小语种试用方法：登陆开放平台https://www.xfyun.cn/后，在控制台--语音听写（流式）--方言/语种处添加
 * 添加后会显示该方言/语种的参数值
 *
 * @author iflytek
 */

public class WebXunFeiTWS {
    private static final String appid = "6ee9b206"; //在控制台-我的应用获取
    private static final String apiSecret = "M2E4ZDZhN2U3OTM3ZjVkOWE5YzYwNzQw"; //在控制台-我的应用-语音听写（流式版）获取
    private static final String apiKey = "b29a8ede0fd5269cc80a9e0762b48815"; //在控制台-我的应用-语音听写（流式版）获取
    private static volatile WebXunFeiTWS instance;
    private static final Object lock = new Object();

    private WebXunFeiTWS() {
        // 私有化构造方法
    }

    public static WebXunFeiTWS getInstance() {
        if (instance == null) {
            synchronized (lock) {
                if (instance == null) {
                    instance = new WebXunFeiTWS();
                }
            }
        }
        return instance;
    }

    private WebSocketListener webSocketListener = new WebSocketListener() {

        @Override
        public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
            super.onClosed(webSocket, code, reason);
            isWebSocketRunning = false;
        }

        @Override
        public void onOpen(WebSocket webSocket, Response response) {
            super.onOpen(webSocket, response);
            stopWebSocketClientTag = false;
            startWebSocketThread();
//        status = 0;
        }

        @Override
        public void onMessage(WebSocket webSocket, String text) {
            super.onMessage(webSocket, text);
            //System.out.println(text);
            ResponseData resp = json.fromJson(text, ResponseData.class);
            if (resp != null) {
                if (resp.getCode() != 0) {
                    System.out.println("code=>" + resp.getCode() + " error=>" + resp.getMessage() + " sid=" + resp.getSid());
                    System.out.println("错误码查询链接：https://www.xfyun.cn/document/error-code");

                    if (resp.getCode() == 10165) { //无效的句柄
                        webSocket.close(1000, "");
                        reconnectWebSocket();
                    } else if (resp.getCode() == 11200) {
                        LogUtil.e("test==>" + "错误：语言未获得授权");
                    } else if (resp.getCode() == 11201) {
                        LogUtil.e("test==>" + "日流控超限");
                    }
                    return;
                }
                if (resp.getData() != null) {
                    if (resp.getData().getResult() != null) {
                        Text te = resp.getData().getResult().getText();
//                        System.out.println("未解析识别结果 ==》" + te.toString());
                        try {
                            decoder.decode(te);
                            System.out.println("中间识别结果 ==》" + decoder.toString());
                            if (onIATWSRecognizeListener != null) {
                                onIATWSRecognizeListener.onWebAsrResult(decoder.toString(), 1);
                                onIATWSRecognizeListener.onWebStartSpeaking();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    if (resp.getData().getStatus() == 2) {
                        // todo  resp.data.status ==2 说明数据全部返回完毕，可以关闭连接，释放资源
                        System.out.println("session end ");
                        dateEnd = new Date();
                        System.out.println(sdf.format(dateBegin) + "开始");
                        System.out.println(sdf.format(dateEnd) + "结束");
                        System.out.println("耗时:" + (dateEnd.getTime() - dateBegin.getTime()) + "ms");
                        System.out.println("最终识别结果 ==》" + decoder.toString());
                        System.out.println("本次识别sid ==》" + resp.getSid());

                        if (onIATWSRecognizeListener != null) {
                            onIATWSRecognizeListener.onWebAsrResult(decoder.toString(), 2);
                        }

                        decoder.discard();
                        webSocket.close(1000, "");

                        reconnectWebSocket();
                    } else {
                        // todo 根据返回的数据处理
                    }
                }
            }
        }

        @Override
        public void onFailure(WebSocket webSocket, Throwable t, Response response) {
            super.onFailure(webSocket, t, response);
            try {
                if (response != null) {
                    int code = response.code();
                    System.out.println("onFailure code:" + code);
                    System.out.println("onFailure body:" + response.body().string());
                    if (code != 101) {
                        System.out.println("connection failed");
                        // 改为日志记录或通知用户，而不是直接退出程序
                        // LogUtil.e("WebSocket连接失败，错误码：" + code);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
    public static volatile boolean isWebSocketRunning = false;
    private static final String hostUrl_def = "https://iat-api.xfyun.cn/v2/iat"; //中英文，http url 不支持解析 ws/wss schema
    private static final String hostUrl_localism = "https://iat-niche-api.xfyun.cn/v2/iat";//小语种
    private static String mHostUrl = hostUrl_def;
    private static String PCM_AUDIO_PATH = new File(Environment.getExternalStorageDirectory(), "test_1.pcm").getPath();

    public static final int StatusFirstFrame = 0;
    public static final int StatusContinueFrame = 1;
    public static final int StatusLastFrame = 2;
    public static final Gson json = new Gson();
    Decoder decoder = new Decoder();
    // 开始时间
    private static Date dateBegin = new Date();
    // 结束时间
    private static Date dateEnd = new Date();

    private static WebSocket webSocket = null;
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss.SSS");

    public void onDestroy() {
        if (webSocket != null) {
            webSocket.cancel(); // 关闭WebSocket连接
            webSocket = null;
        }

        if (onIATWSRecognizeListener != null) {
            onIATWSRecognizeListener = null;
        }
    }

    private static int mAccentLanguageType = 1;  //语言 1普通话/他国语种， 2方言
    private static String mAccent = "mandarin";
    private static String mLanguage = "zh_cn";
    public static int statusInSend = 0;

    public static void sendData(byte[] buffer) {
        if (webSocket == null) {
            LogUtil.e("test==>webSocket 未初始化");
            isWebSocketRunning = false;
            return;
        }

        switch (statusInSend) {
            case StatusFirstFrame:   // 第一帧音频status = 0
                System.out.println(" 第一帧音频status = 0");
                JsonObject frame = new JsonObject();
                JsonObject business = new JsonObject();  //第一帧必须发送
                JsonObject common = new JsonObject();  //第一帧必须发送
                JsonObject data = new JsonObject();  //每一帧都要发送
                // 填充common
                common.addProperty("app_id", appid);
                //填充business
                business.addProperty("language", mLanguage);
                business.addProperty("vad_eos", 1300);
                business.addProperty("domain", "iat");
                if (mAccentLanguageType == 2) {
                    business.addProperty("language", "zh_cn");
                    business.addProperty("accent", mAccent);//中文方言请在控制台添加试用，添加后即展示相应参数值
                }
                //business.addProperty("nunum", 0);
                business.addProperty("ptt", 1);//标点符号
                //business.addProperty("rlang", "zh-hk"); // zh-cn :简体中文（默认值）zh-hk :繁体香港(若未授权不生效，在控制台可免费开通)
                //business.addProperty("vinfo", 1);
                business.addProperty("dwa", "wpgs");//动态修正(若未授权不生效，在控制台可免费开通)
                //填充data
                data.addProperty("status", StatusFirstFrame);
                data.addProperty("format", "audio/L16;rate=16000");
                data.addProperty("encoding", "raw");
                data.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, buffer.length)));
                //填充frame
                frame.add("common", common);
                frame.add("business", business);
                frame.add("data", data);
                webSocket.send(frame.toString());
                statusInSend = StatusContinueFrame;  // 发送完第一帧改变status 为 1
                break;
            case StatusContinueFrame:  //中间帧status = 1
//                System.out.println("中间帧status = 1");
                JsonObject frame1 = new JsonObject();
                JsonObject data1 = new JsonObject();
                data1.addProperty("status", StatusContinueFrame);
                data1.addProperty("format", "audio/L16;rate=16000");
                data1.addProperty("encoding", "raw");
                data1.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, buffer.length)));
                frame1.add("data", data1);
                webSocket.send(frame1.toString());
                // System.out.println("send continue");
                break;
            case StatusLastFrame:    // 最后一帧音频status = 2 ，标志音频发送结束
                System.out.println("最后一帧音频status = 2");
                JsonObject frame2 = new JsonObject();
                JsonObject data2 = new JsonObject();
                data2.addProperty("status", StatusLastFrame);
                data2.addProperty("audio", "");
                data2.addProperty("format", "audio/L16;rate=16000");
                data2.addProperty("encoding", "raw");
                frame2.add("data", data2);
                webSocket.send(frame2.toString());
                System.out.println("send last");
                break;
        }

    }

    private static boolean stopWebSocketClientTag = false;

    public void stopWebSocketThread() {
        stopWebSocketClientTag = true;
        statusInSend = StatusLastFrame;
        // 发送结束标识
        sendData(new byte[10]);

        if (onIATWSRecognizeListener != null) {
            onIATWSRecognizeListener.onWebRecordStatus(false);
        }
    }

    /**
     * APP主动发送最后一帧，尝试触发 vad
     */
    public void sendEndData() {
        statusInSend = StatusLastFrame;
        // 发送结束标识
        sendData(new byte[10]);
    }

    private void startWebSocketThread() {
        new Thread(() -> {
            if (webSocket == null) {
                LogUtil.e("webSocket 未初始化");
                return;
            }

            if (onIATWSRecognizeListener != null) {
                onIATWSRecognizeListener.onWebRecordStatus(true);
            }

            System.out.println("onOpen:连接成功，开始发送数据");
            statusInSend = 0;
            isWebSocketRunning = true;

            while (!stopWebSocketClientTag) {
                try {
                    Thread.sleep(100); // 适当休眠，降低CPU占用
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }

            if (webSocket != null) {
                isWebSocketRunning = false;
                webSocket.close(1000, "");
            }
        }).start();
    }

    private void reconnectWebSocket() {
        try {
            Thread.sleep(250); // 等待n毫秒再重新连接
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        runWebIATWSWebSocket();
    }

    private static final ExecutorService executorService = Executors.newCachedThreadPool();

    public void runWebIATWSWebSocket() {
        executorService.submit(() -> {
            try {
                if (webSocket != null) {
                    webSocket.cancel();
                    webSocket = null;
                }

                //防止数据错乱，把上一次的连接数据丢出去，清空
                String lastResult = decoder.toString();
                if (!lastResult.isEmpty()){
                    if (onIATWSRecognizeListener != null) {
                        onIATWSRecognizeListener.onWebAsrResult(lastResult, 2);
                    }
                    decoder.discard();
                }

                isWebSocketRunning = false;
                String authUrl = getAuthUrl(mHostUrl, apiKey, apiSecret);

                OkHttpClient client = new OkHttpClient.Builder().build();
                WeakReference<OkHttpClient> mClient = new WeakReference<>(client);

                String url = authUrl.replace("http://", "ws://").replace("https://", "wss://");

                Request request = new Request.Builder().url(url).build();
                WeakReference<Request> mRequest = new WeakReference<>(request);

                System.out.println(mClient.get().newCall(mRequest.get()).execute());
                System.out.println("url===>" + url);

                WeakReference<WebSocketListener> mWebSocketListener = new WeakReference<>(webSocketListener);
                webSocket = mClient.get().newWebSocket(mRequest.get(), mWebSocketListener.get());
            } catch (Exception e) {
                LogUtil.e(e.toString());
            }
        });
    }

    public String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
        URL url = new URL(hostUrl);
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = format.format(new Date());
        StringBuilder builder = new StringBuilder("host: ").append(url.getHost()).append("\n").//
                append("date: ").append(date).append("\n").//
                append("GET ").append(url.getPath()).append(" HTTP/1.1");
        //System.out.println(builder);
        Charset charset = Charset.forName("UTF-8");
        Mac mac = Mac.getInstance("hmacsha256");
        SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256");
        mac.init(spec);
        byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset));
        String sha = Base64.getEncoder().encodeToString(hexDigits);

        //System.out.println(sha);
        String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
        //System.out.println(authorization);
        HttpUrl httpUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath()).newBuilder().//
                addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset))).//
                addQueryParameter("date", date).//
                addQueryParameter("host", url.getHost()).//
                build();
        return httpUrl.toString();
    }


    public static class ResponseData {
        private int code;
        private String message;
        private String sid;
        private Data data;

        public int getCode() {
            return code;
        }

        public String getMessage() {
            return this.message;
        }

        public String getSid() {
            return sid;
        }

        public Data getData() {
            return data;
        }
    }

    public static class Data {
        private int status;
        private Result result;

        public int getStatus() {
            return status;
        }

        public Result getResult() {
            return result;
        }
    }

    public static class Result {
        int bg;
        int ed;
        String pgs;
        int[] rg;
        int sn;
        Ws[] ws;
        boolean ls;
        JsonObject vad;

        public Text getText() {
            Text text = new Text();
            StringBuilder sb = new StringBuilder();
            for (Ws ws : this.ws) {
                sb.append(ws.cw[0].w);
            }
            text.sn = this.sn;
            text.text = sb.toString();
            text.sn = this.sn;
            text.rg = this.rg;
            text.pgs = this.pgs;
            text.bg = this.bg;
            text.ed = this.ed;
            text.ls = this.ls;
            text.vad = this.vad == null ? null : this.vad;
            return text;
        }
    }

    public static class Ws {
        Cw[] cw;
        int bg;
        int ed;
    }

    public static class Cw {
        int sc;
        String w;
    }

    public static class Text {
        int sn;
        int bg;
        int ed;
        String text;
        String pgs;
        int[] rg;
        boolean deleted;
        boolean ls;
        JsonObject vad;

        @Override
        public String toString() {
            return "Text{" +
                    "bg=" + bg +
                    ", ed=" + ed +
                    ", ls=" + ls +
                    ", sn=" + sn +
                    ", text='" + text + '\'' +
                    ", pgs=" + pgs +
                    ", rg=" + Arrays.toString(rg) +
                    ", deleted=" + deleted +
                    ", vad=" + (vad == null ? "null" : vad.getAsJsonArray("ws").toString()) +
                    '}';
        }
    }

    //解析返回数据，仅供参考
    public static class Decoder {
        private Text[] texts;
        private int defc = 100;
        public Decoder() {
            this.texts = new Text[this.defc];
        }

        public synchronized void decode(Text text) {
            if (text.sn >= this.defc) {
                this.resize();
            }
            if ("rpl".equals(text.pgs)) {
                for (int i = text.rg[0]; i <= text.rg[1]; i++) {
                    this.texts[i].deleted = true;
                }
            }
            this.texts[text.sn] = text;
        }

        public String toString() {
            return getText();
        }

        private String getText() {
            StringBuilder sb = new StringBuilder();
            for (Text t : this.texts) {
                if (t != null && !t.deleted) {
                    sb.append(t.text);
                }
            }
            return sb.toString();
        }

        public void resize() {
            int oc = this.defc;
            this.defc <<= 1;
            Text[] old = this.texts;
            this.texts = new Text[this.defc];
            for (int i = 0; i < oc; i++) {
                this.texts[i] = old[i];
            }
        }

        public void discard() {
            for (int i = 0; i < this.texts.length; i++) {
                this.texts[i] = null;
            }
        }
    }

    public void clearDiscard() {
        decoder.discard();
    }

    /**
     * 识别语言设置
     **/
    @Deprecated
    public void setScene(String lang) {
        mLanguage = lang;
        mHostUrl = hostUrl_def;
    }

    /**
     * 识别语言设置
     **/
    public void setScene(String lang, String accent, int accentType) {
        mLanguage = lang;
        mAccent = accent;
        mAccentLanguageType = accentType;
//        LogUtil.e("test=====>" + lang);
        if (lang.equals("zh_cn")) {
            mHostUrl = hostUrl_def;
        } else {
            mHostUrl = hostUrl_localism;
        }
        reconnectWebSocket();
    }


    public StringBuffer stringBuffer = new StringBuffer();
    public String asrResult = "";

    public void clearRecognitionText() {
        asrResult = "";
        if (stringBuffer.length() > 0) {
            stringBuffer.setLength(0);
        }
    }


    /*********************************接口回调处理***********************************/
    private OnIATWSRecognizeListener onIATWSRecognizeListener;

    public void setOnIATWSRecognizeListener(OnIATWSRecognizeListener onIATWSRecognizeListener) {
        this.onIATWSRecognizeListener = onIATWSRecognizeListener;
    }

    public interface OnIATWSRecognizeListener {
        void onWebAsrResult(String result, int status);

        void onWebRecordStatus(boolean recording);

        void onWebStartSpeaking();
    }
}